﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using VisualWip.Core.Columns;
using VisualWip.Core.Interfaces;
using VisualWip.Core.Utils;

namespace VisualWip.Core
{
    public class Application : INotifyPropertyChanged, IPersistable
    {
        private readonly ObservableCollection<Column> _columns;
        private readonly IPersistanceProvider _persistanceProvider;
        private readonly List<SettingsPath> _recentSettingsPaths;
        private readonly ObservableCollection<Window> _windows;
        private SettingsPath _currentSettingsPath;
        private bool _isAutoRefreshOn;
        private bool _isDesignMode;
        private DateTime _refreshedAt;
        private bool _snapToGrid;
        private int _zoomLevel = 100;
        private bool _isAutoRecordOn;
        public event EventHandler Refreshing;
        public event EventHandler RefreshDone;



        public Application(IList<SettingsPath> recentSettingsPaths, IPersistanceProvider persistanceProvider)
        {
            _persistanceProvider = persistanceProvider;
            _windows = new ObservableCollection<Window>();
            _columns = new ObservableCollection<Column>();
            _recentSettingsPaths = new List<SettingsPath>();

            if (recentSettingsPaths.Count > 0)
                _recentSettingsPaths.AddRange(recentSettingsPaths);
            RefreshTimeSpan = new TimeSpan(0, 0, 0, 30);
            _refreshedAt = DateTime.MinValue;
        }

        public DateTime RefreshedAt
        {
            get { return _refreshedAt; }
            set
            {
                if (value != _refreshedAt)
                {
                    _refreshedAt = value;
                    OnPropertyChanged(this, () => RefreshedAt);
                }
            }
        }

        public Window MainWindow
        {
            get { return _windows[0]; }
        }

        public ObservableCollection<Window> Windows
        {
            get { return _windows; }
        }

        public ObservableCollection<Column> Columns
        {
            get { return _columns; }
        }

        public IList<SettingsPath> RecentSettingsPaths
        {
            get { return _recentSettingsPaths; }
        }

        public bool IsDesignMode
        {
            get { return _isDesignMode; }
            set
            {
                if (value != _isDesignMode)
                {
                    _isDesignMode = value;

                    if (value == false)
                        Refresh(true);

                    OnPropertyChanged(this, () => IsDesignMode);
                }
            }
        }

        public int ZoomLevel
        {
            get { return _zoomLevel; }
            set
            {
                if (value != _zoomLevel)
                {
                    _zoomLevel = value;
                    OnPropertyChanged(this, () => ZoomLevel);
                }
            }
        }

        public SettingsPath CurrentSettingsPath
        {
            get
            {
                if (_currentSettingsPath == null)
                    return new NoSettingsPath();

                return _currentSettingsPath;
            }
            set
            {
                if (value != _currentSettingsPath)
                {
                    _currentSettingsPath = value;
                    OnPropertyChanged(this, () => CurrentSettingsPath);
                }
            }
        }

        public bool IsAutoRefreshOn
        {
            get { return _isAutoRefreshOn; }
            set
            {
                if (value != _isAutoRefreshOn)
                {
                    _isAutoRefreshOn = value;
                    OnPropertyChanged(this, () => IsAutoRefreshOn);
                    if (_isAutoRefreshOn)
                        Refresh(true);
                }
            }
        }

        public TimeSpan RefreshTimeSpan { get; set; }

        public bool SnapToGrid
        {
            get { return _snapToGrid; }
            set
            {
                if (value != _snapToGrid)
                {
                    _snapToGrid = value;
                    OnPropertyChanged(this, () => SnapToGrid);
                }
            }
        }

        public bool IsAutoRecordOn
        {
            get { return  _isAutoRecordOn; }
            set { if(value != _isAutoRecordOn)
            {
                _isAutoRecordOn = value;
                OnPropertyChanged(this,()=> IsAutoRecordOn);
            }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IPersistable Members

        public PersistenceTree Persist(string name)
        {
            var settings = new PersistenceTree(name, this);
            settings.Add(() => IsDesignMode);
            settings.Add(() => ZoomLevel);
            settings.Add(() => IsAutoRefreshOn);
            settings.Add(() => IsAutoRecordOn);
            settings.AddChildren(() => Windows);
            settings.AddChildren(() => Columns);
            return settings;
        }

        public void Load(PersistenceTree settings)
        {
            Clear();

            IsDesignMode = settings.Get(() => IsDesignMode, false);
            ZoomLevel = settings.Get(() => ZoomLevel, 100);
            IsAutoRefreshOn = settings.Get(() => IsAutoRefreshOn, false);
            IsAutoRecordOn = settings.Get(() => IsAutoRecordOn, false);

            foreach (PersistenceTree child in settings.GetChildren(() => Windows))
            {
                var window = child.Create<Window>();

                if (!window.IsMainWindow)
                    AddWindow(window);
            }
            foreach (PersistenceTree child in settings.GetChildren(() => Columns))
            {
                AddColumn(child.Create<Column>(), autoSetPosition: false, refreshAsync: false);
            }
        }

        #endregion

        public void Open(SettingsPath settingsPath)
        {
            _persistanceProvider.Load(this, settingsPath);
            AddToRecentSettingsPath(settingsPath);
        }

        private void AddToRecentSettingsPath(SettingsPath settingsPath)
        {
            _recentSettingsPaths.Remove(settingsPath);
            _recentSettingsPaths.Insert(0, settingsPath);
            CurrentSettingsPath = settingsPath;

            OnPropertyChanged(this, () => RecentSettingsPaths);
        }

        public void Save(SettingsPath settingsPath)
        {
            _persistanceProvider.Persist(this, settingsPath);
            AddToRecentSettingsPath(settingsPath);
        }

        public void Clear()
        {
            foreach (Column column in Columns)
            {
                RemoveColumn(column);
            }
            Columns.Clear();

            Reset();
        }

        public void Reset()
        {
            ZoomLevel = 100;
            OnResetBoard();
        }

        public event EventHandler ResetBoard;

        protected virtual void OnResetBoard()
        {
            if (ResetBoard != null)
                ResetBoard(this, EventArgs.Empty);
        }

        public void AddWindow()
        {
            Window window;
            if (_windows.Count < 1)
                window = new Window {WindowName = Window.MainWindowName};
            else
                window = new Window
                             {WindowName = string.Format("Window {0}", _windows.Count), WindowNumber = _windows.Count};

            AddWindow(window);
        }

        private void AddWindow(Window window)
        {
            Windows.Add(window);
        }

        public void Refresh(bool refreshAsync)
        {
            OnRefreshing();
            foreach (Column column in Columns)
                column.Refresh(refreshAsync);

            RefreshedAt = DateTime.Now;
            OnRefreshDone();
        }

        protected void OnRefreshing()
        {
            if (Refreshing != null)
                Refreshing(this, EventArgs.Empty);
        }

        protected virtual void OnRefreshDone()
        {
            if (RefreshDone != null)
                RefreshDone(this, EventArgs.Empty);
        }


        public void AddColumn(Column column, bool autoSetPosition)
        {
            AddColumn(column, autoSetPosition, true);
        }

        public void AddColumn(Column column, bool autoSetPosition, bool refreshAsync)
        {
            AddToWindow(column, autoSetPosition);
            Columns.Add(column);

            column.PropertyChanged += ColumnChanged;
            column.Refresh(refreshAsync);
        }

        private void ColumnChanged(object sender, PropertyChangedEventArgs e)
        {
            var column = sender as Column;
            if (column == null)
                return;

            if (e.PropertyName == PropertyHelper.Name(() => column.WindowName))
            {
                MoveColumn(column);
            }
        }

        private void MoveColumn(Column column)
        {
            Window oldWindow = Windows.FirstOrDefault(x => x.WindowColumns.Contains(column));

            if (oldWindow != null)
            {
                oldWindow.WindowColumns.Remove(column);
            }
            else
            {
                foreach (Window window in Windows)
                {
                    window.WindowColumns.Remove(column);
                }
            }
            AddToWindow(column, true);
        }

        private void AddToWindow(Column column, bool autoSetPosition)
        {
            Window window = Windows.FirstOrDefault(x => x.WindowName == column.WindowName) ?? Windows.First();

            if (autoSetPosition)
                ColumnPositioner.CalculatePosition(column, window);

            window.WindowColumns.Add(column);
        }

        public void Init()
        {
            AddWindow();
        }

        public void RemoveColumn(Column column)
        {
            column.PropertyChanged -= ColumnChanged;
            foreach (Window window in Windows)
                window.WindowColumns.Remove(column);

            //Columns.Remove(column);
        }

        protected void OnPropertyChanged<T>(object sender, Expression<Func<T>> property)
        {
            PropertyHelper.OnPropertyChanged(sender, PropertyChanged, property);
        }

        public void DeleteWindow(Window window)
        {
            foreach (Column windowColumn in window.WindowColumns)
            {
                Columns.Remove(windowColumn);
            }
            Windows.Remove(window);
        }
    }
}